﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Imaging;
using System.Windows.Threading;
using MemoryGames.Helpers;

namespace MemoryGames.Games.Memory
{
	/// <summary>
	/// Interaction logic for Memory.xaml
	/// </summary>
	public partial class Main : UserControl, IGameControl
	{
		#region Members
        
		/// <summary>
		/// Specifies the time to wait before closing two open cards 
		/// </summary>
		private const int OPENCARDSWAITTIME = 1000;

		private Random RANDOM = new Random((int)DateTime.Now.Ticks);

		private string imagesPath;
		private int numHorizontalCards;
		private int numVerticalCards;
		private int cellWidth;
		private int cellHeight;
		private bool gameFinished;
		private List<string> images = new List<string>();

		private DispatcherTimer timer = new DispatcherTimer();

		#endregion

		#region Events
		public event MainForm.MainFormRestartDelegate MainFormRestart;

		#endregion

		#region Constructors
		public Main()
		{
			InitializeComponent();
			btnBack.Click += Back_Click;
		}
		#endregion

		/// <summary>
		/// Create Q and A controls
		/// </summary>
		public bool Initialize()
		{
			try
			{
				// Load settings and values
				imagesPath = Configuration.Settings.Load("Memory", "ImagePath");
				numHorizontalCards = Configuration.Settings.Load("Memory", "NumberOfHorizontalCards").ParseIntDefault(4);
				numVerticalCards = Configuration.Settings.Load("Memory", "NumberOfVerticalCards").ParseIntDefault(4);
				
				// Determine the maximum size to use for the cards
				int bounds = (int)Math.Min(this.Height, this.Width) - 200;
				cellHeight = bounds / numVerticalCards;
				cellWidth = bounds / numHorizontalCards;

				LoadImages();
				LoadCards();

				return true;
			}
			catch(MemoryGameException ex)
			{
				MessageBox.Show(ex.Message, "Memory", MessageBoxButton.OK, MessageBoxImage.Warning);
			}
			return false;
		}

		/// <summary>
		/// Loads a filepaths collection of random images 
		/// </summary>
		private void LoadImages()
		{
			// Check if the image path exists
			if (!Directory.Exists(imagesPath))
				throw new MemoryGameException("Het pad naar de afbeeldingen is niet bekend of bestaat niet.");

			// Get a collection of JPG image files
			string[] files = System.IO.Directory.GetFiles(imagesPath, "*.jpg");

			//GK Make sure the number of available images is bigger then the number of cards / 2
			if (files.Length < (numHorizontalCards * numVerticalCards) / 2)
				throw new MemoryGameException("Te weinig afbeeldingen om het Memory spel te laden.");

			string file = files[RANDOM.Next(files.Length)];
			for(int i=0; i< (numHorizontalCards * numVerticalCards) / 2; i++)
			{
				while (images.Contains(file))
					file = files[RANDOM.Next(files.Length)];

				images.Add(file);
			}
			// Double each image
			images.AddRange(images);
		}

		/// <summary>
		/// Creates and displays the cards
		/// </summary>
		private void LoadCards()
		{
			int count = 0;
			for (int i = 0; i < numHorizontalCards; i++)
			{
				for (int j = 0; j < numVerticalCards; j++)
				{
					Card3D c = new Card3D(i * cellWidth, j * cellHeight);
					c.CardClicked += new Action<Card3D>(HandleCardClicked);
					c.RotationCompleted += new Action<Card3D>(HandleRotationCompleted);

					//GK Get the next random image from the collection
					int ix = RANDOM.Next(images.Count);
					string file = images[ix];
					images.RemoveAt(ix);

					c.CardImageFront.Source = new BitmapImage(new Uri(file, UriKind.RelativeOrAbsolute));
					c.Margin = new Thickness(2);
					c.Width = cellWidth;
					c.Height = cellHeight;
					c.Name = GetSafeName(System.IO.Path.GetFileNameWithoutExtension(file));
					c.Id = count;
					c.FlipCount = 0;

					MemoryGrid.Children.Add(c);

					count++;
				}
			}
		}

		/// <summary>
		/// Creates a valid name for use with UI elements based on the given name
		/// </summary>
		/// <param name="name"></param>
		/// <returns></returns>
		private static string GetSafeName(string name)
		{
			string safe = "ID";
			foreach (char c in name)
			{
				if (char.IsLetterOrDigit(c))
					safe += c;
			}
			return safe;
		}

		/// <summary>
		/// Checks if all cards are matched and start game end effect
		/// </summary>
		private void CheckGameComplete()
		{
			if (gameFinished) return;

			foreach(Card3D card in MemoryGrid.Children)
			{
				if (!card.Matched) return;
			}

			// Show win message
			lblFinishedMessage.Visibility = Visibility.Visible;

			gameFinished = true;
		}

		#region Event Handlers
		private void Back_Click()
		{
			this.MainGrid.Children.Clear();
			this.MainFormRestart.Invoke();
		}

		private List<Card3D> openCards = new List<Card3D>();
		private void HandleCardClicked(Card3D card)
		{
			// Do nothing if the clicked card is match or currently rotating
			if (card.Matched || card.IsRotating) return;

			// Do not process click events when another card is rotating
			foreach (Card3D c in openCards)
				if (c.IsRotating) return;

			lock (openCards)
			{
				if (openCards.Count < 2 && card.State == CardState.Closed)
				{
					openCards.Add(card);
					card.RotateCard();
				}
				else if (openCards.Count == 2)
				{
					// Wait till each card has been rotated
					foreach (Card3D c in openCards)
						if (c.IsRotating) return;

					foreach (Card3D c in openCards)
					{
						if(c.Matched) continue;
						c.RotateCard();

						// Stop potential timer
						if(timer.IsEnabled) timer.Stop();
					}
					openCards.Clear();	

					// Add new card
					openCards.Add(card);
					card.RotateCard();
				}
			}
		}

		private void HandleRotationCompleted(Card3D obj)
		{
			// When two cards are open, check if they're the same
			lock (openCards)
			{
				if (openCards.Count == 2 && openCards[0].Name == openCards[1].Name)
				{
					// There is match, put cards in the end of the list so they will be on top of the stack
					MemoryGrid.Children.Remove(openCards[0]);
					MemoryGrid.Children.Add(openCards[0]);
					MemoryGrid.Children.Remove(openCards[1]);
					MemoryGrid.Children.Add(openCards[1]);

					// Start visual effect on cards
					openCards[0].Matched = true;
					openCards[1].Matched = true;
					openCards[0].PutOnStack(0);
					openCards[1].PutOnStack(0);

					// Check if all cards are matched
					CheckGameComplete();
				}
				else if(openCards.Count == 2)
				{
					// Start timer to close cards after x seconds
					timer.Interval = TimeSpan.FromMilliseconds(OPENCARDSWAITTIME);
					timer.Tick += new EventHandler(timer_Tick);
					timer.Start();
				}
			}
		}

		private void timer_Tick(object sender, EventArgs e)
		{
			timer.Stop();

			// Close open cards
			foreach (Card3D c in openCards)
			{
				if (c.Matched) continue;
				c.RotateCard();

				// Stop potential timer
				if (timer.IsEnabled) timer.Stop();
			}
			openCards.Clear();	

		}

		#endregion
	}
}